package demoMod.stsconsole.monsters;

import demoMod.stsconsole.ConsoleEntry;
import demoMod.stsconsole.core.AbstractCreature;
import demoMod.stsconsole.dungeons.AbstractDungeon;
import demoMod.stsconsole.helpers.DamageInfo;
import demoMod.stsconsole.helpers.LocalizedStrings;
import demoMod.stsconsole.interfaces.*;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

public abstract class AbstractMonster extends AbstractCreature {
    protected List<DamageInfo> damage = new ArrayList<>();
    protected int nextMove;
    protected List<MoveInfo> moveHistory = new ArrayList<>();
    public List<String> monsterStrings;
    protected boolean firstMove = true;
    public AbstractCreature defaultTarget;
    protected int block;
    public EnemyType type;

    public AbstractMonster(String id) {
        super(id);
        onReloadStrings();
        this.defaultTarget = AbstractDungeon.player;
        this.type = EnemyType.ENEMY;
    }

    public void gainGold() {
        switch (this.type) {
            case ENEMY:
                gainGold(5 + AbstractDungeon.monsterRng.nextInt(6));
                break;
            case ELITE:
                gainGold(15 + AbstractDungeon.monsterRng.nextInt(6));
                break;
            case BOSS:
                gainGold(100 + AbstractDungeon.monsterRng.nextInt(21));
                break;
        }
    }

    public void setHp(int min, int max) {
        if (min < max) {
            this.currentHealth = this.maxHealth = min + AbstractDungeon.monsterRng.nextInt(max - min + 1);
        } else {
            this.currentHealth = this.maxHealth = min;
        }
    }

    protected abstract void getMove(int aiRng);

    protected void setMove(int moveNum, Intent intent) {
        setMove(moveNum, intent, "", false, 0, null);
    }

    protected void setMove(int moveNum, Intent intent, DamageInfo info) {
        setMove(moveNum, intent, "", false, 1, info);
    }

    protected void setMove(int moveNum, Intent intent, DamageInfo info, int multi) {
        setMove(moveNum, intent, "", true, multi, info);
    }

    protected void setMove(int moveNum, Intent intent, String moveName) {
        setMove(moveNum, intent, moveName, false, 0, null);
    }

    protected void setMove(int moveNum, Intent intent, String moveName, DamageInfo info) {
        setMove(moveNum, intent, moveName, false, 1, info);
    }

    protected boolean lastMove(int moveNum) {
        if (this.moveHistory.size() < 1) return false;
        return this.moveHistory.get(this.moveHistory.size() - 1).moveNum == moveNum;
    }

    public boolean lastTwoMoves(int moveNum) {
        return lastTwoMoves(moveNum, moveNum);
    }

    public boolean lastTwoMoves(int moveNum1, int moveNum2) {
        if (this.moveHistory.size() < 2) return false;
        return this.moveHistory.get(this.moveHistory.size() - 1).moveNum == moveNum1 && this.moveHistory.get(this.moveHistory.size() - 2).moveNum == moveNum2;
    }

    protected void setMove(int moveNum, Intent intent, String moveName, boolean isMultiDamage, int multi, DamageInfo info) {
        MoveInfo moveInfo = new MoveInfo();
        moveInfo.moveNum = moveNum;
        moveInfo.intent = intent;
        moveInfo.moveName = moveName;
        moveInfo.multi = multi;
        moveInfo.isMultiDamage = isMultiDamage;
        moveInfo.info = info;
        this.moveHistory.add(moveInfo);
        this.nextMove = moveNum;
    }

    public void changeMove(int moveNum, Intent intent, String moveName, boolean isMultiDamage, int multi, DamageInfo info) {
        MoveInfo moveInfo = new MoveInfo();
        moveInfo.moveNum = moveNum;
        moveInfo.intent = intent;
        moveInfo.moveName = moveName;
        moveInfo.multi = multi;
        moveInfo.isMultiDamage = isMultiDamage;
        moveInfo.info = info;
        this.moveHistory.set(this.moveHistory.size() - 1, moveInfo);
        this.nextMove = moveNum;
    }

    public void usePreBattleAction() {

    }

    public abstract void takeTurn();

    public void rollMove() {
        getMove(AbstractDungeon.aiRng.nextInt(100));
        applyPowers();
    }

    public MoveInfo getCurrentMove() {
        return moveHistory.isEmpty() ? null : moveHistory.get(moveHistory.size() - 1);
    }

    public void applyPowers() {
        if (this.defaultTarget == null) {
            this.defaultTarget = AbstractDungeon.player;
        }
        for (DamageInfo info : this.damage) {
            info.applyPowers(this.defaultTarget);
        }
    }

    @Override
    public void gainBlock(int blockAmount) {
        this.block = blockAmount;
        ConsoleEntry.triggerSubscribers(BlockSubscriber.class,
                "onDefendToChangeBlock",
                new Class[]{int.class, AbstractCreature.class},
                new Object[]{blockAmount, this},
                (method, params, subscriber) -> {
                    try {
                        params[0] = method.invoke(subscriber, params);
                        block = (int) params[0];
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
        );
        List<String> commonStrings = LocalizedStrings.getCharacterStrings("AbstractPlayer");
        this.talk(String.format(commonStrings.get(10), this.block));
        super.gainBlock(this.block);
    }

    @Override
    public void damage(DamageInfo info) {
        if (this.isDead) return;
        ConsoleEntry.triggerSubscribers(DamageSubscriber.class,
                "onAttack",
                new Class[]{float.class, DamageInfo.class, AbstractCreature.class},
                new Object[]{info.output, info, this}
        );
        List<String> commonStrings = LocalizedStrings.getCharacterStrings("AbstractPlayer");
        this.talk(commonStrings.get(7));
        if (info.output > 0) {
            if (info.type == DamageInfo.DamageType.HP_LOSS) {
                this.lastDamageTaken = Math.min((int) info.output, this.currentHealth);
                this.currentHealth -= info.output;
                this.talk(String.format(commonStrings.get(8), (int) info.output));
            } else {
                float tmp = info.output;
                info.output -= this.currentBlock;
                this.currentBlock -= tmp;
                if (this.currentBlock < 0) this.currentBlock = 0;
                if (info.output > 0) {
                    this.lastDamageTaken = Math.min((int) info.output, this.currentHealth);
                    this.currentHealth -= info.output;
                    this.talk(String.format(commonStrings.get(8), (int) info.output));
                } else {
                    this.talk(commonStrings.get(9));
                }
            }
            if (info.output > 0) {
                ConsoleEntry.triggerSubscribers(InflictSubscriber.class, "onInflictDamage", new Class[]{DamageInfo.class, AbstractCreature.class}, new Object[]{info, this});
            }
            if (this.currentHealth <= 0) {
                this.currentHealth = 0;
                ConsoleEntry.triggerSubscribers(ResurrectSubscriber.class, "onResurrect", new Class[]{AbstractCreature.class}, new Object[]{this});
                if (this.currentHealth <= 0) {
                    this.die();
                }
            }
        } else {
            this.talk(commonStrings.get(9));
        }
    }

    @Override
    public void die() {
        this.isDead = true;
        ConsoleEntry.triggerSubscribers(MonsterDeathSubscriber.class, "onMonsterDeath", new Class[]{AbstractMonster.class}, new Object[]{this});
        this.onRemove();
        AbstractDungeon.currDungeon.currMapNode.room.goldAmount += this.gold;
        if (AbstractDungeon.currDungeon.currMapNode.room.combatManager.isEnemyBasicallyDead()) {
            AbstractDungeon.currDungeon.currMapNode.room.combatManager.endCombat();
        }
    }

    @Override
    public void escape() {
        this.isEscaped = true;
        this.gold = 0;
        this.onRemove();
        if (AbstractDungeon.currDungeon.currMapNode.room.combatManager.isEnemyBasicallyDead()) {
            AbstractDungeon.currDungeon.currMapNode.room.combatManager.endCombat();
        }
    }

    @Override
    public void onReloadStrings() {
        this.monsterStrings = LocalizedStrings.getMonsterStrings(this.id);
        this.name = monsterStrings.get(0);
    }

    public static class MoveInfo {
        public int moveNum;
        public Intent intent;
        public String moveName;
        public boolean isMultiDamage;
        public int multi;
        public DamageInfo info;
    }

    public enum Intent {
        ATTACK,
        DEFEND,
        ATTACK_DEFEND,
        DEBUFF,
        ATTACK_DEBUFF,
        DEFEND_DEBUFF,
        BUFF,
        ATTACK_BUFF,
        DEFEND_BUFF,
        BUFF_DEBUFF,
        STRONG_DEBUFF,
        UNKNOWN,
        STUNNED,
        SLEEPING,
        ESCAPE
    }

    public enum EnemyType {
        ENEMY, ELITE, BOSS
    }
}
